package main

import "fmt"

//Slice切片——Go语言内置类型
//slice的运行时结构就是一个三元组{array,len,cap},即指向底层数组的指针、切片长度(元素数量)以及切片的最大容量
//slice的优势: 1.可以自动扩容 2.它是数组的描述符,在传参时大小是固定了,避免了内存拷贝的开销
//最佳实践:在创建slice时,最好准确预估容量,避免频繁扩容时的复制操作操作额外的性能开销

func main() {
	//declareSlice()

	//subSlice()

	//sliceFromArray()

	sliceDynamicScaling()
}

// 声明切片
func declareSlice() {
	//方式一: 声明slice并显式初始化,此时len=cap=初始化元素的数量
	s1 := []int{1, 2, 3, 4}
	//通过len函数获取切片长度,cap函数获取切片容量
	fmt.Printf("s1: %v\tlen: %d\tcap: %d\n", s1, len(s1), cap(s1)) //len=4 cap=4

	//方式二: 通过内置函数make创建slice,并指定len和cap
	s2 := make([]int, 2, 10)
	fmt.Printf("s2: %v\tlen: %d\tcap: %d\n", s2, len(s2), cap(s2)) //len=2 cap=10

	//方式三: 通过内置函数make创建slice,只指定len,此时默认cap=len
	s3 := make([]int, 4)
	fmt.Printf("s3: %v\tlen: %d\tcap: %d\n", s3, len(s3), cap(s3)) //len=4 cap=4
}

// 数组的切片化——基于底层数组创建切片
// 采用array[low:high:max]
// 切片就类似于一个访问与修改数组的窗口,切片之于数组,就像文件描述符之于文件
// 数组更多是“退居幕后”,承担底层存储空间的角色
func sliceFromArray() {
	//采用array[low:high:max]语法,生成的切片len=high-low cap=max-low 切片元素范围为array[low,high)
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s := arr[3:7:9]
	//s={3,4,5,6} len=7-3=4 cap=9-3=6
	fmt.Printf("s: %v\tlen: %d\tcap: %d\n", s, len(s), cap(s))

	//通常可以省略max,此时max默认=底层数组长度
	s1 := arr[1:5]
	//s1={1,2,3,4} len=5-1=4 cap=len(arr)-1=10-1=9
	fmt.Printf("s1: %v\tlen: %d\tcap: %d\n", s1, len(s1), cap(s1))

	//因为多个切片共享同一个底层数组,所以任意一个切片对元素的修改,都会同步反映到底层数组和切片中
	s1[2] += 100
	fmt.Println("s1切片修改元素值")
	fmt.Printf("s的值为: %v\n", s)
	fmt.Printf("底层数组的值为: %v\n", arr)

	//当一个切片发生扩容,长度超过底层数组长度时,它就与底层数组“解除绑定”,对该切片的修改就不会影响底层数组了
	for i := 0; i < 9; i++ {
		s1 = append(s1, i+10)
	}

	s1[3] += 100
	fmt.Println("s1扩容后,修改元素值")
	fmt.Printf("s的值为: %v\n", s)
	fmt.Printf("底层数组的值为: %v\n", arr)
}

// 数组和切片都可以通过slice[start:end]的形式生成子切片
// 取值范围为[start,end)区间
// 子切片与原数组(切片)共享同一个底层数组
// 当子切片的结构发生变化,主要就是发生扩容时,便不再与原数组(切片)共享同一个底层数组
func subSlice() {
	s := []int{0, 1, 2, 3, 4, 5, 6}
	fmt.Printf("s: %v\tlen: %d\tcap: %d\n", s, len(s), cap(s))
	s1 := s[1:5] //[start,end)
	fmt.Printf("s1: %v\tlen: %d\tcap: %d\n", s1, len(s1), cap(s1))
	s2 := s[3:] //[start,len)
	fmt.Printf("s2: %v\tlen: %d\tcap: %d\n", s2, len(s2), cap(s2))
	s3 := s[:4] //[0,end)
	fmt.Printf("s3: %v\tlen: %d\tcap: %d\n", s3, len(s3), cap(s3))
}

// 重切片
// slice的完整定义是：array[low:high:max] 生成的切片len=high-low,cap=max-low。
// 通常省略可以省略max，即array[low:high],此时max的默认值为数组的长度。
func reSlice() {
	//创建底层数组
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	//基于数组创建切片
	s1 := arr[1:9] //low=1 high=9 max=默认数组长度=10 len=high-low=9-1=8 cap=max-low=10-1=9
	fmt.Printf("s1: %v\tlen: %d\tcap: %d\n", s1, len(s1), cap(s1))

	//re-slice
	s2 := s1[2:6] //low=2 high=6 max=默认数组长度=9 len=high-low=4 cap=max-low=9-2=7
	fmt.Printf("s2: %v\tlen: %d\tcap: %d\n", s2, len(s2), cap(s2))
}

// 切片的动态扩容
// 切片在进行append操作时,会自动扩容,扩容后的容量为原来的2倍
// 扩容后会创建新的底层数组,并将所有元素都拷贝到新数组中,并将旧数组垃圾回收掉
// 频繁扩容会造成大量的内存拷贝性能开销,在使用切片时最好在一开始就预估好容量
func sliceDynamicScaling() {
	var s []int
	s = append(s, 1)
	fmt.Printf("s: %v\tlen: %d\tcap: %d\n", s, len(s), cap(s)) //len=1 cap=1

	s = append(s, 2)
	fmt.Printf("s: %v\tlen: %d\tcap: %d\n", s, len(s), cap(s)) //len=2 cap=2

	s = append(s, 3)
	fmt.Printf("s: %v\tlen: %d\tcap: %d\n", s, len(s), cap(s)) //len=3 cap=4

	s = append(s, 4)
	fmt.Printf("s: %v\tlen: %d\tcap: %d\n", s, len(s), cap(s)) //len=4 cap=4

	s = append(s, 5)
	fmt.Printf("s: %v\tlen: %d\tcap: %d\n", s, len(s), cap(s)) //len=5 cap=8
}
